//
// Created by feng on 19-4-14.
//

#ifndef SOLARENERGYRAYTRACING_RECTGRIDDDA_CUH
#define SOLARENERGYRAYTRACING_RECTGRIDDDA_CUH

#include <algorithm/rayTracing/qmcrt_util/global_constant.h>
#include <algorithm/rayTracing/qmcrt_util/global_function.cuh>
#include <algorithm/rayTracing/rayTracingDataStructure/grid/CudaRectangleGrid.cuh>
#include <algorithm/rayTracing/rayTracingPipeline/quasiMonteCarloRayTracing/RayTracingArgumentStruct.h>
#include <cuda_runtime.h>

/**
    注意：一般，函数在.h文件中声明，在.cpp文件中实现，这里把实现也一并写到了.h文件中
         是为了解决cuda编译时报错的问题（ptxas fatal：undefined reference to '_ZN11_....'）
         把函数实现写到.h文件中，会导致multiple definition的问题，但是以下情况是例外：
                1-内联函数
                2-class的定义
                3-const和static类型
*/

namespace solar::cuda::rectGridDDA
{
    template <typename T>
    inline __host__ __device__ auto absDivide(const T& denominator, const T& numerator) -> T
    {
        if (numerator <= Epsilon && numerator >= -Epsilon)
        {
            return T(INT_MAX);
        }
        return std::abs(denominator / numerator);
    }

    inline __host__ __device__ auto calTMax(float dir, float interval, int current_index,
                                            float current_pos) -> float
    {
        return std::abs(float(current_index + static_cast<int>(dir >= 0)) * interval - current_pos);
    }

    inline __host__ __device__ auto equal(float n1, float n2) -> bool
    {
        return (n1 < n2 + Epsilon) && (n1 > n2 - Epsilon);
    }

    inline __host__ __device__ auto less(float n1, float n2) -> bool { return n1 < n2 - Epsilon; }

    /**
     * Intersect with heliostats in this rectangle grid.
     */
    // inline __host__ __device__ bool intersect(const float3 &origin, const float3 &dir, const
    // float3 *d_heliostat_vertexes,
    //                                    const int *d_grid_heliostat_match, int start_id, int
    //                                    end_id, int heliostat_id, int numberOfSubHeliostat);

    inline __host__ __device__ auto testdevicecuda_in_rectGridDDA() -> bool
    {
        printf("test rectGridDDA\n");
        return false;
    }

    // bool transfer_to_testdevicecuda_in_rectGridDDA()
    // {
    //     return testdevicecuda_in_rectGridDDA();
    // }

    inline __host__ __device__ auto intersect(const float3& origin, const float3& dir,
                                              const float3* d_heliostat_vertexes,
                                              const int* d_grid_heliostat_match, int start_id,
                                              int end_id, int subheliostat_id,
                                              int numberOfSubHeliostat) -> bool
    {
        float t, u, v;
        for (int i = start_id; i < end_id; ++i)
        {
            int subHeliostatIndex = d_grid_heliostat_match[i];
            if (subHeliostatIndex < subheliostat_id ||
                subHeliostatIndex >= subheliostat_id + numberOfSubHeliostat)
            {
                if (global_func::rayParallelogramIntersect(
                        origin, dir, d_heliostat_vertexes[3 * subHeliostatIndex + 1],
                        d_heliostat_vertexes[3 * subHeliostatIndex],
                        d_heliostat_vertexes[3 * subHeliostatIndex + 2], t, u, v))
                {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 3D-DDA
     */
    inline __host__ __device__ auto collision(const float3& origin, const float3& dir,
                                              const CudaRectangleGrid& rectGrid,
                                              const float3* d_subheliostat_vertexes,
                                              const HeliostatArgument& heliostatArgument) -> bool;
    inline __host__ __device__ auto collision_(const float3& origin, const float3& dir,
                                               const CudaRectangleGrid& rectGrid,
                                               const float3* d_subheliostat_vertexes,
                                               const HeliostatArgument& heliostatArgument,
                                               int id) -> bool
    {
        // printf("=== enter rectGridDDA::collision_()\n");
        /**
         *Step 1 - Initialization
         **/

        //  Step 1.1 Initial current position of origin in the scene.

        int3 pos = make_int3((origin - rectGrid.getPosition()) / rectGrid.getInterval());

        //  Step 1.2 StepX, StepY, StepZ
        int3 step;
        step.x = (dir.x >= 0) ? 1 : -1;
        step.y = (dir.y >= 0) ? 1 : -1;
        step.z = (dir.z >= 0) ? 1 : -1;

        //  Step 1.3 Initial tmaxX, tmaxY, tmaxZ
        float3 t_max; // avoid divide 0
        // tMax = |pos ± interval - (origin-rectGrid.getPosition())| / dir
        t_max.x = absDivide(
            calTMax(dir.x, rectGrid.getInterval().x, pos.x, origin.x - rectGrid.getPosition().x),
            dir.x);
        t_max.y = absDivide(
            calTMax(dir.y, rectGrid.getInterval().y, pos.y, origin.y - rectGrid.getPosition().y),
            dir.y);
        t_max.z = absDivide(
            calTMax(dir.z, rectGrid.getInterval().z, pos.z, origin.z - rectGrid.getPosition().z),
            dir.z);

        //  Step 1.4 Initial tDeltaX, tDeltaY, tDeltaZ
        float3 t_delta;
        t_delta.x = absDivide(rectGrid.getInterval().x, dir.x);
        t_delta.y = absDivide(rectGrid.getInterval().y, dir.y);
        t_delta.z = absDivide(rectGrid.getInterval().z, dir.z);

        /**
         * Step 2 - Intersection
         **/

        int3 grid_index = pos;
        int grid_address = 0;

        while (grid_index.x >= 0 && grid_index.x < rectGrid.getSubGridNumber().x &&
               grid_index.y >= 0 && grid_index.y < rectGrid.getSubGridNumber().y &&
               grid_index.z >= 0 && grid_index.z < rectGrid.getSubGridNumber().z)
        {

            grid_address = global_func::unroll_index(grid_index, rectGrid.getSubGridNumber());

            if (intersect(origin, dir, d_subheliostat_vertexes,
                          rectGrid.getDeviceGridHeliostatMatch(),
                          rectGrid.getDeviceGridHeliostatIndex()[grid_address],
                          rectGrid.getDeviceGridHeliostatIndex()[grid_address + 1],
                          heliostatArgument.subHeliostat_id[id], heliostatArgument.helionums))
            {
                return true;
            }

            // next cell location
            bool x_min_flag = false;
            bool y_min_flag = false;
            bool z_min_flag = false;

            if (less(t_max.x, t_max.y))
            { // x < y
                if (less(t_max.x, t_max.z) || equal(t_max.x, t_max.z))
                { // x <= z,  +x minimal
                    grid_index.x += step.x;
                    x_min_flag = true;
                }
                if (less(t_max.z, t_max.x) || equal(t_max.x, t_max.z))
                { // z <= x,  +z minimal
                    grid_index.z += step.z;
                    z_min_flag = true;
                }
            }
            else if (less(t_max.y, t_max.x))
            { // y < x
                if (less(t_max.y, t_max.z) || equal(t_max.y, t_max.z))
                { // y <= z,  +y minimal
                    grid_index.y += step.y;
                    y_min_flag = true;
                }
                if (less(t_max.z, t_max.y) || equal(t_max.y, t_max.z))
                { // z <= y,  +z minimal
                    grid_index.z += step.z;
                    z_min_flag = true;
                }
            }
            else
            { // x == y
                if (less(t_max.x, t_max.z) || equal(t_max.x, t_max.z))
                { // x(y) <= z, +x&y minimal
                    grid_index.x += step.x;
                    grid_index.y += step.y;
                    x_min_flag = y_min_flag = true;
                }
                if (less(t_max.z, t_max.x) || equal(t_max.x, t_max.z))
                { // z <= x(y),   +z minimal
                    grid_index.z += step.z;
                    z_min_flag = true;
                }
            }
            t_max.x += (x_min_flag) ? t_delta.x : 0;
            t_max.y += (y_min_flag) ? t_delta.y : 0;
            t_max.z += (z_min_flag) ? t_delta.z : 0;
        }

        return false;
    }

    // inline _host__ __device__ bool collision_(const float3 &origin, const float3 &dir, const
    // CudaRectangleGrid &rectGrid,
    //                                             const float3 *d_subheliostat_vertexes,
    //                                             const HeliostatArgument &heliostatArgument,int
    //                                             id) {

    // printf("=== enter rectGridDDA::collision_()\n");
    /**
     *Step 1 - Initialization
     **/

    // //  Step 1.1 Initial current position of origin in the scene.

    // int3 pos = make_int3((origin - rectGrid.getPosition()) / rectGrid.getInterval());

    // //  Step 1.2 StepX, StepY, StepZ
    // int3 Step;
    // Step.x = (dir.x >= 0) ? 1 : -1;
    // Step.y = (dir.y >= 0) ? 1 : -1;
    // Step.z = (dir.z >= 0) ? 1 : -1;

    // //  Step 1.3 Initial tmaxX, tmaxY, tmaxZ
    // float3 tMax;    // avoid divide 0
    // // tMax = |pos ± interval - (origin-rectGrid.getPosition())| / dir
    // tMax.x = absDivide( calTMax(dir.x, rectGrid.getInterval().x, pos.x,
    // origin.x-rectGrid.getPosition().x) , dir.x ); tMax.y = absDivide( calTMax(dir.y,
    // rectGrid.getInterval().y, pos.y, origin.y-rectGrid.getPosition().y) , dir.y ); tMax.z =
    // absDivide( calTMax(dir.z, rectGrid.getInterval().z, pos.z, origin.z-rectGrid.getPosition().z)
    // , dir.z );

    // //  Step 1.4 Initial tDeltaX, tDeltaY, tDeltaZ
    // float3 tDelta;
    // tDelta.x = absDivide(rectGrid.getInterval().x, dir.x);
    // tDelta.y = absDivide(rectGrid.getInterval().y, dir.y);
    // tDelta.z = absDivide(rectGrid.getInterval().z, dir.z);

    // /**
    // * Step 2 - Intersection
    // **/

    // int3 grid_index = pos;
    // int grid_address;

    // while(grid_index.x >= 0 && grid_index.x < rectGrid.getSubGridNumber().x &&
    //      grid_index.y >= 0 && grid_index.y < rectGrid.getSubGridNumber().y &&
    //      grid_index.z >= 0 && grid_index.z < rectGrid.getSubGridNumber().z){

    //    grid_address = global_func::unroll_index(grid_index, rectGrid.getSubGridNumber());

    //    if(intersect(origin, dir, d_subheliostat_vertexes,
    //                 rectGrid.getDeviceGridHeliostatMatch(),
    //                 rectGrid.getDeviceGridHeliostatIndex()[grid_address],
    //                 rectGrid.getDeviceGridHeliostatIndex()[grid_address + 1],
    //                 heliostatArgument.subHeliostat_id[id],
    //                 heliostatArgument.helionums)){
    //        return true;
    //    }

    //    // next cell location
    //    bool xMinFlag, yMinFlag, zMinFlag;
    //    xMinFlag = yMinFlag = zMinFlag = false;

    //    if(less(tMax.x, tMax.y)){   // x < y
    //        if(less(tMax.x, tMax.z) || equal(tMax.x, tMax.z)){  // x <= z,  +x minimal
    //            grid_index.x += Step.x;
    //            xMinFlag = true;
    //        }
    //        if(less(tMax.z, tMax.x) || equal(tMax.x, tMax.z)){  // z <= x,  +z minimal
    //            grid_index.z += Step.z;
    //            zMinFlag = true;
    //        }
    //    }else if(less(tMax.y, tMax.x)){ // y < x
    //        if(less(tMax.y, tMax.z) || equal(tMax.y, tMax.z)){  // y <= z,  +y minimal
    //            grid_index.y += Step.y;
    //            yMinFlag = true;
    //        }
    //        if(less(tMax.z, tMax.y) || equal(tMax.y, tMax.z)){  // z <= y,  +z minimal
    //            grid_index.z += Step.z;
    //            zMinFlag = true;
    //        }
    //    }else{  // x == y
    //        if(less(tMax.x, tMax.z) || equal(tMax.x, tMax.z)){  // x(y) <= z, +x&y minimal
    //            grid_index.x += Step.x;
    //            grid_index.y += Step.y;
    //            xMinFlag = yMinFlag = true;
    //        }
    //        if(less(tMax.z, tMax.x) || equal(tMax.x, tMax.z)){  // z <= x(y),   +z minimal
    //            grid_index.z += Step.z;
    //            zMinFlag = true;
    //        }
    //    }
    //    tMax.x += (xMinFlag) ? tDelta.x : 0;
    //    tMax.y += (yMinFlag) ? tDelta.y : 0;
    //    tMax.z += (zMinFlag) ? tDelta.z : 0;
    // }

    // return false;
    // //return true;
    // return false;

    // }

} // namespace solar::cuda::rectGridDDA

#endif // SOLARENERGYRAYTRACING_RECTFRIDDDA_CUH
